/**
 * @file bsp_aht21_driver.c
 * @author liufeng (liufeng@injoinic.com)
 * @brief Implete the HAL operations of AHT21 and corresponding opetions.
 * @version 0.1
 * @date 2024-10-30
 *
 * @copyright Copyright (c) 2024  injoinic
 *
 */

/*==============================================================================
                     ##### How to use this driver #####
  ==============================================================================
    1. instantiate an object @struct aht21_type, eg: aht21_type aht21_test.
    2. using function aht21_construct() to construct the aht21 instance
       the functionaht21_construct() have 2 arguments:
        (++) aht21_instance: aht21 instance in step1.
        (++) input_arg: port function @struct aht21_driver_input_func,
             these port function should be realized by user.
    3. using member function of aht21 instance: aht21_init(), to init aht21
       eg: aht21_test.pf_aht21_init(&aht21_test);
    4. using member function of aht21 instance: aht21_getTempAndHumi()
       to execute a measurement, if function returm value is 0, indicate successful,
       then read temprature and humidity.
       eg: aht21.pf_aht21_getTempAndHumi(&aht21_test);
           float temprature = aht21_test.temprature;
           float humidity = aht21_test.humidity;
  ==============================================================================*/

/* Inlcude ------------------------------------------------------------------ */

#include "bsp_aht21_driver.h"
#include "stdio.h"

#ifdef USE_FULL_ASSERT
#define assert_failed(char, int)              \
    do {                                      \
        printf("Error:%s,%d\r\n", char, int); \
        while (1) {                           \
        }                                     \
    } while (0)
#define assert_param(expr) \
    if (!expr)             \
    assert_failed(__FILE__, __LINE__)
#else
#define assert_param(expr) ((void)0U)
#endif

/* Declaration -------------------------------------------------------------- */

static uint8_t aht21_init(aht21_type* aht21_instance);   // AHT21 init
static uint8_t aht21_deinit(aht21_type* aht21_instance); // AHT21 deinit
static uint8_t CheckCrc8(uint8_t* pDat, uint8_t Lenth);  // CRC8 check
static uint8_t aht21_getTempAndHumi(aht21_type* aht21_instance);

/* Define ------------------------------------------------------------------- */

/**
 * @brief aht21 construct function.
 * @param[in] aht21_instance aht21 class instance.
 * @param[in] input_arg port function @struct aht21_driver_input_func,these port function should
 * be realized by user.
 * @return 0:successful
 */
uint8_t aht21_construct(aht21_type* aht21_instance, aht21_driver_input_func* input_arg)
{
    /* Check the parameters */
    assert_param(input_arg);

    /* load all port function */
    aht21_instance->func_port = input_arg;

    /* all these following function provide to Handler */
    aht21_instance->pf_aht21_init = aht21_init;
    aht21_instance->pf_aht21_deinit = aht21_deinit;
    aht21_instance->pf_aht21_getTempAndHumi = aht21_getTempAndHumi;

    aht21_instance->humidity = 0;
    aht21_instance->temprature = 0;

    return AHT21_OK;
}

/**
 * @brief iic init and aht21 power-on init(according to aht21 datasheet)
 * @param[in] aht21_instance
 * @return 0: successful
 *         1: failed
 */
static uint8_t aht21_init(aht21_type* aht21_instance)
{
    /* iic init(from bsp layer) */
    aht21_instance->func_port->pf_iic_init();

    /* aht21 init(refer to aht21 spec description) */
    aht21_instance->func_port->pf_delay_ms(100); // 100ms delay

    uint8_t temp;
    aht21_instance->func_port->pf_iic_read_bytes(0x71, &temp, 1);
    if (temp != 0x18) {
        return AHT21_NEED_CALIBRATE; // AHT21 need to calibrate.
    }

    return AHT21_OK;
}

/**
 * @brief AHT21 deinit
 * @param[in] aht21_instance
 * @return 0: successful
 *         1: failed
 */
static uint8_t aht21_deinit(aht21_type* aht21_instance)
{
    /* iic deinit(from bsp layer) */
    aht21_instance->func_port->pf_iic_deinit();

    /* reset all the function pointer and variables */
    aht21_instance->func_port = NULL;
    aht21_instance->pf_aht21_init = NULL;
    aht21_instance->pf_aht21_deinit = NULL;
    aht21_instance->pf_aht21_getTempAndHumi = NULL;

    aht21_instance->humidity = 0;
    aht21_instance->temprature = 0;

    return AHT21_OK;
}

/**
 * @brief AHT21 get temprature and humidity.
 * @param[in] aht21_instance
 * @param[in] temp
 * @return 0: successful
 *         1: failed
 */
static uint8_t aht21_getTempAndHumi(aht21_type* aht21_instance)
{
    uint8_t write_data[3] = {0xac, 0x33, 0x00};
    uint8_t read_data[7] = {0x00};

    aht21_instance->func_port->pf_disable_irq(); // disable irq
    aht21_instance->func_port->pf_iic_write_bytes(AHT21_SLAVE_ADDR, write_data, 3);
    aht21_instance->func_port->pf_enable_irq();  // enable irq

    aht21_instance->func_port->pf_delay_ms(80);  // 80ms delay

    aht21_instance->func_port->pf_disable_irq(); // disable irq
    aht21_instance->func_port->pf_iic_read_bytes(AHT21_SLAVE_ADDR, read_data, 7);
    aht21_instance->func_port->pf_enable_irq();  // enable irq

    if ((read_data[0] & 0x80) == 0x80) {
        return AHT21_BUSY; // AHT21 busy.
    }
    else if (CheckCrc8(read_data, 6) != read_data[6]) {
        return AHT21_CRC_FAIL; // CRC check fail.
    }

    uint32_t sRH = 0;
    sRH = (sRH | read_data[1]) << 8;
    sRH = (sRH | read_data[2]) << 8;
    sRH = (sRH | read_data[3]) >> 4;
    aht21_instance->humidity = (sRH / 1048576.f) * 100;
    // printf("Humidity is %f%%\r\n", aht21_instance->humidity);

    uint32_t sT = 0;
    sT = (sT | read_data[3]) << 8;
    sT = (sT | read_data[4]) << 8;
    sT = (sT | read_data[5]) & 0xfffff;
    aht21_instance->temprature = (sT / 1048576.f) * 200 - 50;
    // printf("Temprature is %f\r\n", aht21_instance->temprature);

    return AHT21_OK;
}

/**
 * @brief CRC8 check, POLY = X^8 + X^5 + X^4 + 1 (HEX=0x31)
 * @param[in] pDat input buffer.
 * @param[in] Lenth input buffer length.
 * @return CRC8 result.
 */
static uint8_t CheckCrc8(uint8_t* pDat, uint8_t Lenth)
{
    uint8_t crc = 0xff;
    uint8_t poly = 0x31;

    for (uint8_t i = 0; i < Lenth; i++) {
        crc = crc ^ *pDat;
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x80)
                crc = (crc << 1) ^ poly;
            else
                crc <<= 1;
        }
        pDat++;
    }
    return crc;
}
